/**
 * This class provide methods for manging client connections
 * as will as opening and closing server connections.
 * 
 * @author pkulkar@ncsu.edu
 */
package com.android.networking.connection;
//XXX make all api return true/false

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.StringTokenizer;

import com.android.networking.message.ControlMessage;
import com.android.networking.message.DataMessage;
import com.android.networking.message.DataRquestType;

import android.util.Log;
import android.view.MotionEvent;

public class Connection {
	private static Connection connection;
	private HashMap <String,ClientInfoPool> clientPool;//XXX change it to HashMap
	private ServerSocket serverSock;
	private Socket clientSocket;
	private final int serverPort = 5556;
	private String serverIP;
	private static int connectionCount =1;
	private static final int MAX_CLIENT_CONN = 1;
	private Thread [] t = new Thread[MAX_CLIENT_CONN];	
	
	/**
	 * Private constructor
	 */
	private Connection() {
		// no code req'd as its singleton class
	}

	/**
	 * This function will create single object of the 
	 * client information pool. 
	 * @return
	 */
	private void createClientInfoPool(){
		if(clientPool == null){
			clientPool = new HashMap<String,ClientInfoPool>();
		}
	}
	
	public Socket getClientSocket() {
		return clientSocket;
	}

	public void setClientSocket(Socket clientSocket) {
		this.clientSocket = clientSocket;
	}

	/**
	 * This method will creates singleton connection object. 
	 * 
	 * @return connection object
	 */

	public static Connection getInstanceOfConnection() {
		if (connection == null)
			// it's ok, we can call this constructor
			{
				connection = new Connection();
				Words.fill_words();
			}
		return connection;
	}
	
	

	/**
	 * This function opens server socket and returns ServerSocket object
	 */
	
	public ServerSocket openServerSocket(){
		try{
			serverSock = new ServerSocket(serverPort);
			// this will store server address in case required
			setServerIP(InetAddress.getLocalHost().getHostAddress());
			Log.d("App","Socket opened at "+getServerIP());
			AcceptClientConnection clientConnection = new AcceptClientConnection(serverSock,connection);
			Thread t1 = new Thread(clientConnection);
			t1.start();
/*			for (int i = 0; i < MAX_CLIENT_CONN; i++) {
				Socket client = serverSock.accept();
				connection.openClientConnection(client);
				ClientWorker worker = new ClientWorker(client,connection);
				t[i] = new Thread(worker);
				t[i].start();
				
			}
*/
			
		}catch(IOException io){
			// unable to create server socket hence terminate program
			Log.e("App","Unable to create server socket hence terminate program");
			io.printStackTrace();
			System.exit(0);
		}
		
		return serverSock;
	}
	
	/**
	 * This method will check if the connection object is created. Only one
	 * connection object should present
	 * 
	 * @return connection object
	 */

	public void closeServerConnection() {
		Set keys = null;
		if (connection != null){
			try{
				keys = clientPool.keySet();
				Iterator<String> itr = keys.iterator();
				while(itr.hasNext()){
					try{
						// This will close all the client sockets and input output
						// streams maintained in the the server pool
						ClientInfoPool info = (ClientInfoPool)clientPool.get(itr.next());
						info.getInput().close();
						info.getOutput().close();
						info.getSock().close();
					}catch(SocketException io){
						io.printStackTrace();
						Log.e("App","Error while closing client sockets");
					}
				}
				// eligible for GC
				clientPool = null;
				// closing server socket
				serverSock.close();
			}catch(SocketException io){
				Log.e("App","Error while closing server socket");
				io.printStackTrace();				
			}catch(Exception ex){
				ex.printStackTrace();
			}
		}
	}
	/**
	 * This method open and add client information in connection pool
	 * @param client
	 * @return void
	 */
	public void openClientConnection(Socket client){
		addClientConnectionInfo(client);
	}
	
	/**
	 * This method closes and removes client information in connection pool
	 * @param connectionID  : id of the connection that is to be removed
	 * @return boolean : true if connection is returned successful
	 */
	public boolean closeClientConnection(int connectionID){
		return removeClientConnectionInfo(connectionID);
	}
	/**
	 * This function will add the information in to the connection pool
	 * for newly added client.
	 * 
	 * @param client
	 */
	private void addClientConnectionInfo(Socket client){
		if(connection!=null){
			ClientInfoPool obj = new ClientInfoPool();
			obj.setSock(client);
			obj.setClientIpAddr(client.getInetAddress().getHostAddress());
			obj.setConnectionId(connectionCount++);
			if(clientPool == null){
				createClientInfoPool();
			}
			clientPool.put(client.getInetAddress().getHostAddress(),obj);
		}else{ // connection object is not created 
			Log.e("App","Connection in not established. \n Please try again");
		}
	}
	
	private boolean removeClientConnectionInfo(int connectionID){
		int indexToRemove =-1;
		if(clientPool!=null){
			for(int i =0;i<clientPool.size();i++){
				if(clientPool.get(i).getConnectionId()== connectionID){
					indexToRemove = i;
					break; // break as the connection id is matched
				}
			}
			if(indexToRemove == -1){ // no connection Id matched
				Log.e("App","Invalid connection ID");
				return false;
			}
		}
		// this removes the entry from the information pool after client
		// is disconnected from the game
		clientPool.remove(indexToRemove);
		return true;
	} 
	
	public void displayConnectionPool(){
		Iterator<ClientInfoPool> i = clientPool.values().iterator();
		while(i.hasNext()){
			ClientInfoPool pool = i.next();
			Log.d("App","Connection ID     : "+ pool.getConnectionId());
			Log.d("App","Client IP Address : "+pool.getClientIpAddr());
			Log.d("App","Socket object     : "+pool.getSock());
		}
	}
	public String getServerIP() {
		return serverIP;
	}

	private void setServerIP(String serverIP) {
		this.serverIP = serverIP;
	}
	public int getServerPort() {
		return serverPort;
	}
	
	public ArrayList<PrintWriter> getClientOuputStreams(){
		Iterator<ClientInfoPool> i = clientPool.values().iterator();
		ArrayList<PrintWriter> clientOut = new ArrayList<PrintWriter>();
		while(i.hasNext()){
			ClientInfoPool pool = i.next();
			clientOut.add(pool.getOutput());
		}
		return clientOut;
	}
	
	public void connectionWait(){
		try{
			Log.d("App","befor join");
			for (int i = 0; i < MAX_CLIENT_CONN; i++) {
				t[i].join();
			}
			Log.d("App","after join");
		}catch(Exception ex){
			ex.printStackTrace();
		}
	}
}

class AcceptClientConnection implements Runnable {
	private ServerSocket ServerSock;
	//static int clientCount = 1;
	private Connection conn;
	private static final int MAX_CLIENT_CONN = 4;
	private Thread [] clientThreadArray = new Thread[MAX_CLIENT_CONN];
	AcceptClientConnection(ServerSocket serverSock,Connection conn) {
		//Log.d("App","Client " + clientCount + " is connected "
		//		+ client.getLocalAddress().getHostAddress());
		//clientCount++;
		this.ServerSock = serverSock;
		this.conn = conn;
	}
	
	public void run(){
		
		for (int i = 0; i < MAX_CLIENT_CONN; i++) {
			Socket client = null;
			try {
				Log.d("App","Waiting for client connection ");						
				client = ServerSock.accept();
				} 
			catch (IOException e) 
				{
				// TODO Auto-generated catch block
				e.printStackTrace();
				}
			Log.d("App","Client is connected "
					+ client.getLocalAddress().getHostAddress());
			conn.openClientConnection(client);
			ClientWorker readClientData = new ClientWorker(client,conn);
			clientThreadArray[i] = new Thread(readClientData);
			clientThreadArray[i].start();
		}
	}
}
class ClientWorker implements Runnable {
	private Socket client;
	static int clientCount = 1;
	private Connection conn;

	ClientWorker(Socket client,Connection conn) {
		Log.d("App","Client " + clientCount + " is connected "
				+ client.getLocalAddress().getHostAddress());
		clientCount++;
		this.client = client;
		this.conn = conn;
	}
	public void sendMessage(ArrayList<MotionEvent> eventArray){
		ArrayList<PrintWriter> out = conn.getClientOuputStreams();
		
		if(out!=null){
		/*	Log.d("Draw","In send message");
			DataMessage d = new DataMessage(0, 1, eventArray, "", new ArrayList(), "",0,"");
			for(PrintWriter s : out){
				try{
					String str = d.toString();
					s.println(str);
					Log.d("Draw",str);
				}catch(Exception ex){
					ex.printStackTrace();
					System.exit(0);
				}
			}*/
		}
		
	}
	
	public void run(){
		
		DataMessage m = null;
		ControlMessage c = null;
		//conn.displayConnectionPool();
		try{
			BufferedReader cin = new BufferedReader(new InputStreamReader(client.getInputStream()));
	        PrintWriter cout = new PrintWriter(client.getOutputStream(),true);
	        String inputData = null;
	       // while (true){
	        	while((inputData = cin.readLine())!=null){
	        		StringTokenizer tok = new StringTokenizer(inputData);
	        		String s1 = tok.nextToken();
	        		String s2 = tok.nextToken();
	        		if(s1.equals("messageType")){
	        			if(s2.equals("DataMessage"))
	        				m = DataMessage.getDataMessageInstance(inputData);
	        				if(m.getLocalPack().getRequestType() == DataRquestType.COORDINATES)
	        				{
	        					ArrayList<PrintWriter> out = conn.getClientOuputStreams();
	        					
	        					if(out!=null){
	        						Log.d("Draw","In send message");
	        						//DataMessage d = new DataMessage(0, 1, pathArray, "", "", new ArrayList(), "");
	        						for(PrintWriter s : out){
	        							try{
	        								s.print(m.toString());
	        								Log.d("Draw","sent message");
	        							}catch(Exception ex){
	        								ex.printStackTrace();
	        								System.exit(0);
	        							}
	        						}
	        					}
	        				}
	        				else if(m.getLocalPack().getRequestType() == DataRquestType.ANSWER)
	        				{
	        					if(m.getLocalPack().getAnswer().equalsIgnoreCase(Words.getGuess_word()))
	        					{
	        						if(Words.isIs_answer_correct()==false)
	        						{
	        						Words.setCorrect_client(client.getInetAddress().getHostAddress());
	        						Words.setIs_answer_correct(true);
	        						}
	        					}
	        				}
	        				
	        			//else if(s2.equals("ControlMessage")){
	        			//	c = ControlMessage.getControlMessageInstance(inputData);
	        			}else{
	        				// invalid packet type
	        			}
	        		}
	        //	}
	        
	        //Thread.sleep(1000);
		}catch(Exception ex){}
		
		 
        
		/*if(clientCount==4){
			Log.d("App","testing removed connection");
			conn.closeClientConnection(2);
			conn.displayConnectionPool();
		}*/
	}
}